package org.simpleframework.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * @author linchong
 * @version 1.0
 * @Date: 2022-05-21 20:01
 * @Description: ClassUtil$
 */
@Slf4j
public class ClassUtil {


	public static final String FILE_PROTOCOL = "file";

	/**
	 * 根据传入的包名，获取该包下所有的class对象
	 * @param packageName 包名
	 * @return 类集合
	 */
	public static Set<Class<?>>  extractPackageClass(String packageName){
		//获取类加载器->通过类加载器获取到加载的资源信息->根据不同的资源类型，采用不同的方式获取资源的集合
		//1.获取类加载器（解决项目发布的实际路径）
		ClassLoader classLoader = getClassLoader();
		//2.通过类加载器获取到加载的资源
 		URL url = classLoader.getResource(packageName.replace(".", "/"));
		if(url == null){
			log.warn("unable to retrieve anything from package:"+packageName);
			return null;
		}

		//3.根据不同的资源类型，采用不同的方式获取资源的集合
		Set<Class<?>> classSet = null;
		//过滤出文件类型资源
		if(url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
			classSet = new HashSet<Class<?>>();
			File packageDirectory = new File(url.getPath());
			extractClassFile(classSet,packageDirectory,packageName);
		}

		//TODO 此处可以加入针对其他类型资源的处理
		return classSet;

	}

	public static void main(String[] args) {
		extractPackageClass("com.lincong.entity");
	}

	/**
	 * 递归获取目标package中的所有class文件（包括子package中的class文件）
	 * @param emptyClassSet 装载目标类的集合
	 * @param fileSource 文件或者目录
	 * @param packageName 包名
	 *
	 */
	private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
		if(!fileSource.isDirectory()){
			return;
		}
		//如果是文件夹，列出当前文件夹下的文件或文件夹
		File[] files = fileSource.listFiles(new FileFilter() {
			@Override
			public boolean accept(File file) {
				if(file.isDirectory()){
					return true;
				}else{
					//获取文件绝对值路径
					String absoluteFilePath = file.getAbsolutePath();
					if(absoluteFilePath.endsWith(".class")){
						//如果是class文件，直接将其加载
						//将class文件生成类实例，加载
						addToClassSet(absoluteFilePath);
					}
				}
				return false;
			}
			//根据class文件的绝对值路径，获取并生成class对象，并放入到classSet中
			private void addToClassSet(String absoluteFilePath) {
				//1.从class文件的绝对值路径中，获取并生成class对象，并放入classSet中
				//如com/xxx/xxx/UserInfo.class,需要生成com.xxx.xxx.UserInfo对象
				absoluteFilePath = absoluteFilePath.replace(File.separator,".");
				String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
				className = className.substring(0,className.lastIndexOf("."));
				//2.通过反射机制获取对应的class对象并加入到classSet中
				Class targetClass = loadClass(className);
				emptyClassSet.add(targetClass);
			}
		});
		if(files != null){
			for (File file : files) {
				//递归调用
				extractClassFile(emptyClassSet,file,packageName);
			}
		}
	}

	/**
	 * 获取class对象
	 * @param className class全名=package+类名
	 * @return Class
	 */
	public static Class<?> loadClass(String className){
		try {
			return Class.forName(className);
		} catch (ClassNotFoundException e) {
			log.info("load class error",e);
			throw new RuntimeException(e);
		}
	}


	/**
	 * 实例化class
	 * @param clazz Class
	 * @param accessible 是否支持创建出私有（private）class对象的实例
	 * @param <T> Class类型
	 * @return 类实例化
	 */
	public static <T> T newInstance(Class<?> clazz,boolean accessible){
		try {
			Constructor constructor = clazz.getDeclaredConstructor();
			constructor.setAccessible(accessible);
			return (T)clazz.getDeclaredConstructor().newInstance();
		} catch (Exception e) {
			log.error("newInstance error,",e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * 设置类的书写
	 * @param field 成员变量
	 * @param target 类实例
	 * @param value 成员变量的值
	 * @param accessible 是否允许设置私有属性
	 */
	public static void setField(Field field, Object target, Object value, boolean accessible){
		field.setAccessible(accessible);
		try {
			field.set(target,value);
		} catch (IllegalAccessException e) {
			log.error("setField error",e);
			throw new RuntimeException(e);
		}

	}

	/**
	 * 获取classloader
	 * @return 当前classLoader实例
	 */
	public static ClassLoader getClassLoader(){
		return Thread.currentThread().getContextClassLoader();
	}


}
